__version__ = "2.0.1"
__author__ = "ArtLinty"

# MIT License
#
# Copyright (c) 2019 立师兄Linty
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""
case 包 构造方法，包含
    WebCase：WebUI 用例继承的基类
    ApiCase：WebAPI 用例继承的基类
    
！！！ 请勿直接使用 _BaseCase
"""

import time

import allure
import pytest

from base import BoxDriver, Logger, Json
from base.helper import parse_digit


class _BaseCase(object):
    _logger = None
    _url = None

    @property
    def current_time_string(self):
        """
        当前时间的字符串格式
        :return:
        """
        return time.strftime("%Y%m%d_%H%M%S", time.localtime())

    @property
    def current_timestamp(self):
        """
        当前时间戳
        :return: 精确到整数
        """
        return round(time.time(), 0)

    def log(self, msg):
        """
        添加日志
        :param msg:
        :return:
        """
        if self._logger is not None:
            self._logger.info(msg)

    def assert_in(self, expected, actual):
        """
        assert in, expected in actual
        :param expected:
        :param actual:
        :return:
            例如
                "胡艳" in "胡晓艳"  fail
                "胡" in "胡晓艳"  success
        """
        result = expected in actual
        log_msg = "断言 assert_in：{%r} in {%r} 的结果是 {%r}！" % (expected, actual, result)

        self.log(log_msg)
        return result

    def assert_equal(self, expected, actual):
        """
        assert equal
        :param expected:
        :param actual:
        :return: 类型要一致
            例如
                "1" == 1  fail
                1 == 1 success
                str, int, float, boolean( True, False)...
                object 类型不可以相等
                    api1 = NspUserApi()
                    api2 = NspUserApi()
                    api1 == api2  fail
        """
        result = expected == actual
        log_msg = "断言 assert_equal：{%r} == {%r} 的结果是 {%r}！" % (expected, actual, result)

        self.log(log_msg)
        return result

    def assert_int_equal(self, expected, actual):
        """
        assert int equal, 忽略字符串和数字之间的差异
        :param expected:
        :param actual:
        :return:
            例如
                assert_int_equal(1, "1") success
                assert_int_equal(1.0, "1")  success
                assert_int_equal(-1.0, "-1")  success
                assert_int_equal(-1.0, "-1.0")  success
                assert_int_equal(-1, "-1.0")  success
        """
        result = False

        expected = self.convert_str_to_decimal(expected, ndigits=0)
        actual = self.convert_str_to_decimal(actual, ndigits=0)

        if expected is not None and actual is not None:
            result = expected == actual

        log_msg = "断言 assert_int_equal：{%r} == {%r} 的结果是 {%r}！" % (expected, actual, result)

        self.log(log_msg)
        return result

    def assert_decimal_equal(self, expected, actual, ndigits=2):
        """
        assert int equal, 忽略字符串和数值的差别
        :param ndigits: 小数位数
        :param expected:
        :param actual:
        :return:
            例子
                assert_decimal_equal("123.88", 123.88, 2) success
                assert_decimal_equal("123.88", 123.88, 1) success
                assert_decimal_equal("123.88", 123.89, 1) success
                assert_decimal_equal("123.88", 123.89, 2) fail
        """
        result = False

        expected = self.convert_str_to_decimal(expected, ndigits)
        actual = self.convert_str_to_decimal(actual, ndigits)

        if expected is not None and actual is not None:
            result = expected == actual

        log_msg = "断言 assert_decimal_equal：小数位{%r}, {%r} == {%r} 的结果是 {%r}！" \
                  % (ndigits, expected, actual, result)

        self.log(log_msg)
        return result

    def assert_dict_equal(self, expected, actual, data_key, index=None, sub_key=None):
        """
        断言 字典是否相等
        :param expected: 期望的结果 dict 格式
        :param actual: 实际的结果 dict 格式
        :param data_key: data key
        :param index: 下标，默认为 None
        :param sub_key: 子 data key，默认为 None
        :return:
        """
        result = False
        expected_value = self.parse_dict_value(expected, data_key, index, sub_key)
        actual_value = self.parse_dict_value(actual, data_key, index, sub_key)

        if expected_value is not None and actual_value is not None:
            result = expected_value == actual_value

        log_msg = "断言 assert_dict_equal： {%r} == {%r}, data_key=%s, index=%r, sub_key=%s 的结果是 {%r}！" \
                  % (expected, actual, data_key, index, sub_key, result)

        self.log(log_msg)
        return result

    def parse_dict_value(self, json_dict, data_key, index=None, sub_key=None):
        """
        解析字典的值
        :param json_dict:
        :param data_key:
        :param index:
        :param sub_key:
        :return: 如果输入 json_dict 是字典，返回对应的 key，key不存在就返回 None。
            如果 输入 json_dict 不是字典，直接返回 None
        """
        log_msg = "[system] - [%s] parse_dict_value: json_dict=%s, data_key=%s, index=%r, sub_key=%s, " \
                  % (__name__, json_dict, data_key, index, sub_key)

        self.log(log_msg)
        if json_dict is not None and isinstance(json_dict, dict):
            return Json.parse_json_dict_value(
                json_dict=json_dict,
                data_key=data_key,
                index=index,
                sub_key=sub_key
            )
        return None

    def convert_str_to_decimal(self, digit_str, ndigits=None):
        """
        转变 字符串（数字组成的字符串）为数字
        :param ndigits: 数值的小数位数，默认是 None
        :param digit_str: 数字组成的字符串
            规则：
                接受整型 "123"
                接受负数 "-123"
                接受小数 "-123.88"， "123.88"

        :return:
            如果 digit_str 是字符串 str 格式，就转化
            否则，返回 原样的值
        """
        self.log("[system] - [%s] parse_digit: %s" % (__name__, digit_str))
        ret_decimal = digit_str
        if digit_str is not None and isinstance(digit_str, str):
            ret_decimal = parse_digit(digit_str)
        if ndigits is not None and isinstance(ret_decimal, float) and isinstance(ndigits, int):
            ret_decimal = round(ret_decimal, ndigits)
        return ret_decimal

    def convert_json_str_to_dict(self, json_str_to_convert):
        """
        转 json 格式的 字符串 为 Python 字典 dict 类型
        建议使用 bejson.com 编写 json，然后 压缩成为字符串，复制到变量
        :param json_str_to_convert:
        :return: dict
        """
        self.log("[system] - [%s] convert_json_str_to_dict: %s" % (__name__, json_str_to_convert))
        if json_str_to_convert is not None and isinstance(json_str_to_convert, str):
            return Json.convert_json_str_to_dict(json_str_to_convert)

        return None


class WebCase(_BaseCase):
    """
    测试用例类

    """
    _driver = None

    @property
    def driver(self):
        return self._driver

    @pytest.fixture(autouse=True)
    def prepare(self):
        self._driver = BoxDriver(BoxDriver.DriverType.CHROME)
        self._logger = Logger("%s_created_by_%s.log" % (__file__, __name__), __name__)
        self.log("start test case exec!")
        yield
        self.log("finish test case exec!")
        self._driver.quit()

    @allure.step
    def snapshot(self, name):
        """
        snapshot
        :return:
        """
        if self._driver is not None:
            image = self._driver.save_window_snapshot_by_png()
            allure.attach(image, name=name, attachment_type=allure.attachment_type.PNG)

    def assert_in(self, expected, actual):
        """
        assert in
        :param expected:
        :param actual:
        :return:
        """

        result = super(WebCase, self).assert_in(expected, actual)
        log_msg = "断言 assert_in：{%r} in {%r} 的结果是 {%r}！" % (expected, actual, result)

        if not result:
            self.snapshot(log_msg)

        return result

    def assert_equal(self, expected, actual):
        """
        assert equal
        :param expected:
        :param actual:
        :return:
        """
        result = super(WebCase, self).assert_equal(expected, actual)

        log_msg = "断言 assert_equal：{%r} == {%r} 的结果是 {%r}！" % (expected, actual, result)
        if not result:
            self.snapshot(log_msg)

        return result


class ApiCase(_BaseCase):
    """
    API case
    """

    @pytest.fixture(autouse=True)
    def prepare(self):
        self._logger = Logger("%s_created_by_%s.log" % (__file__, __name__), __name__)
        self.log("start test case exec!")
        yield
        self.log("finish test case exec!")
